<!doctype html>
<html>
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width,initial-scale=1">
	<title>Document</title>
	<style>
		*{margin:0;padding:0;list-style:none;}
		ul{width:300px;margin:50px auto;}
		li{width:100%;height:30px;background:#f8f8f8;color:#333;margin:10px 0;text-align:center;line-height:30px;}
	</style>
</head>
<body>
	<ul>
		<li id="li1">第一个列表</li>
		<li id="li2">第二个列表</li>
		<li id="li3">第三个列表</li>
	</ul>
	<script>
		// 元素选择器
		function $(selector){
			var result = [];
	        if(document.querySelectorAll){
	            var res=document.querySelectorAll(selector),
	                i=0,
	                len=res.length;
	            for (;i<len;i++) {
	                result.push(res.item(i));
	            }
	        }else{//兼容IE，IE特有方法
	            var sheet=document.styleSheets.length > 0 ? document.styleSheets[0] : document.createStyleSheet(),
	                elements=document.all,
	                len=elements.length,
	                i=0;
	                sheet.addRule(selector,'xl:xl');
	            for(;i<len;i++) {
	                if(elements[i].currentStyle.xl) result.push(elements[i]);
	            }
	            sheet.removeRule(0);
	        }
	        return result;
		}
		$('ul li')[0].style.background="gray";

		// siblings方法,ie9+支持
		function siblings(ele){
			return Array.prototype.filter.call(ele.parentNode.children,function(child){
			  	return child !== ele;
			});
		}

		// each方法,ie9+支持
		function each(eles,fn){
			return Array.prototype.forEach.call(eles,function(el,i){
				fn.call(el,el,i); //fn的this指向el,带了el和i两个参数
			});
		}
		each(siblings($('#li1')[0]),function(el,i){
			el.style.background="#f40";
			this.style.color='#fff';
		});

		// 自动判断设备跳转手机界面
		var mobileAgent = new Array("iphone", "ipod", "ipad", "android", "mobile", "blackberry", "webos", "incognito", "webmate", "bada", "nokia", "lg", "ucweb", "skyfire");
        var browser = navigator.userAgent.toLowerCase();
        var isMobile = false;
        for (var i = 0; i < mobileAgent.length; i++) {
            if (browser.indexOf(mobileAgent[i]) != -1) {
                isMobile = true;
                alert(mobileAgent[i]);
                // location.href ='mobile.html';
                break;
            }
        }
        // 判断是否为微信端
        function is_weixin() {
		    var ua = navigator.userAgent.toLowerCase();
		    if (ua.match(/MicroMessenger/i) == "micromessenger") {
		        return true;
		    } else {
		        return false;
		    }
		}

	    // siblings方法,ie8+支持
	    function siblings(o){
			var a=[];
			var p=o.previousSibling;
			while(p){
				if(p.nodeType===1){
					a.push(p);
				}
				p=p.previousSibling;
			}
			a.reverse();
			var n=o.nextSibling;
			while(n){
				if(n.nodeType===1){
					a.push(n);
				}
				n=n.nextSibling;
			}
			return a;
		}

		// Cookie
		function setCookie(name,value){
			var Days = 30;
			var exp = new Date();
			exp.setTime(exp.getTime() + Days*24*60*60*1000);
			document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
		}
		function getCookie(name){
			var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
			if(arr=document.cookie.match(reg))
			return unescape(arr[2]);
			else
			return null;
		}
		function delCookie(name){
			var exp = new Date();
			exp.setTime(exp.getTime() - 1);
			var cval=getCookie(name);
			if(cval!=null)
			document.cookie= name + "="+cval+";expires="+exp.toGMTString();
		}

		// getBoundingClientRect方法 
		//document.documentElement.clientTop;  // 非IE为0，IE为2
		//document.documentElement.clientLeft; // 非IE为0，IE为2
		function getRect(ele){
		    var rect = ele.getBoundingClientRect();
		    var top = document.documentElement.clientTop;
		    var left= document.documentElement.clientLeft;
		    return{
		        top    :   rect.top - top,
		        bottom :   rect.bottom - top,
		        left   :   rect.left - left,
		        right  :   rect.right - left
		    };
		}

		// 移动端点击
		function touchClick(obj,fun) {
			var start_x = 0,
				start_y = 0;
			obj.addEventListener('touchstart',function(e){
				start_x = e.touches[0].clientX;
				start_y = e.touches[0].clientY;
				document.addEventListener('touchend', touEnd, false);
			});
			function touEnd(e){
				var endX = e.changedTouches[0].clientX;
				var endY = e.changedTouches[0].clientY;
				if(Math.abs(endX - start_x) < 5 && Math.abs(endY - start_y) < 5) {
					fun.call(obj,e);
				}
				document.removeEventListener('touchend', touEnd, false);
			}
		}

		// 文档加载
		function ready(fn){
		    if(document.addEventListener) {
		        document.addEventListener('DOMContentLoaded', function() {
		            //注销事件, 避免反复触发
		            document.removeEventListener('DOMContentLoaded',arguments.callee, false);
		            fn();            //执行函数
		        }, false);
		    }else if(document.attachEvent) {        //IE
		        document.attachEvent('onreadystatechange', function() {
		            if(document.readyState == 'complete') {
		                document.detachEvent('onreadystatechange', arguments.callee);
		                fn();        //函数执行
		            }
		        });
		    }
		}

		// 去掉首尾空格
		function trim(a){return(a||"").replace(/^\s*(.*?)\s*$/,"$1")}

		// 获取checkbox值
		var value=obj.checked;

		// 页面可见性
		document.addEventListener("visibilitychange", function(){
		    document.title = document.hidden ? "用户离开了" : "用户回来了";
		});

		// 判断ios系统版本号
		var str= navigator.userAgent.toLowerCase(); 
	    var ver=str.match(/cpu iphone os (.*?) like mac os/);
	    if(!ver){
	        alert("请在Ios系统中打开");
	    }else{
	        alert("你当前的Ios系统版本为："+ver[1].replace(/_/g,"."));
	    }

	    // void实现自执行
	    void function test() {
				alert('hello');
			}();

		// 判断是否ie
		function isIE(){
			return !!window.ActiveXObject ||  "ActiveXObject" in window ? true : false;
		}

		// 兼容ie，removeNode()
		function removeNode(){
		　　if(isIE()){
		　　　　obj.parentNode.removeNode(true);
		　　}else{
		 　　　 obj.parentNode.remove();
		 	}
		}

		// 移除一个元素的所有子节点
		var element = document.getElementById("top");
		while(element.firstChild){
			element.removeChild(element.firstChild);
		}

		// 禁止右键和ctrl+s另存为
		document.oncontextmenu=function(){
			return false;//禁止右键菜单
		};
		document.onmousedown=function(e){
			var event=e || window.event;
			if(event.button == 2){
	           return false;//禁止鼠标右键点击
	     	}
		};
		document.onkeydown=function(e){
			var event=e || window.event;
			var key=event.keyCode;
			if(key== 83 && e.ctrlKey){
				setTimeout(function(){  
		            return false;//禁止ctrl+s按键
		        },10);
		        return false;
			}
		};

		// 当前日期
		function CurentTime(){ 
			var now = new Date();       
			var year = now.getFullYear();       //年
			var month = now.getMonth() + 1;     //月
			var day = now.getDate();            //日   
			var clock = year + "-";    
			if(month < 10)
					clock += "0";       
			clock += month + "-";        
			if(day < 10)
					clock += "0";           
			clock += day;       
			return(clock); 
		}

		// 解决键盘弹出后挡表单的问题
		window.addEventListener('resize', function() {
			if(
				document.activeElement.tagName === 'INPUT' ||
				document.activeElement.tagName === 'TEXTAREA'
			) {
				window.setTimeout(function() {
					if('scrollIntoView' in document.activeElement) {
						document.activeElement.scrollIntoView();
					} else {
						document.activeElement.scrollIntoViewIfNeeded();
					}
				}, 0);
			}
		});

		// 判断ie浏览器版本
		function IEVersion() {
			var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串  
			var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; //判断是否IE<11浏览器  
			var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器  
			var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
			if(isIE) {
					var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
					reIE.test(userAgent);
					var fIEVersion = parseFloat(RegExp["$1"]);
					if(fIEVersion == 7) {
							return 7;
					} else if(fIEVersion == 8) {
							return 8;
					} else if(fIEVersion == 9) {
							return 9;
					} else if(fIEVersion == 10) {
							return 10;
					} else {
							return 6;//IE版本<=7
					}   
			} else if(isEdge) {
					return 'edge';//edge
			} else if(isIE11) {
					return 11; //IE11  
			}else{
					return -1;//不是ie浏览器
			}
		}

		// if简写,获取标签内属性名
		function getAttr(el, attrName){
			var attr = {'for':'htmlFor', 'class':'className'}[attrName] || attrName;
		}

		// url参数获取
		// 第一种
		function getUrlPara(para){
			var url=location.search,arr={};
			if(url.indexOf('?')!=-1){
				var code=url.substr(1).split('&');
				for(var i=0;i<code.length;i++){
					var item=code[i].split('=');
					arr[item[0]]=item[1];
				}
				return arr[para];
			}
		}
		// 第二种
		const q={}
		location.search.replace(/([^?&=]+)=([^&]+)/g,(_,k,v)=>q[k]=v)
		console.log(q)

		// 自写apply函数
		Function.prototype.myApply = function (context, args) {
			//这里默认不传就是给window,也可以用es6给参数设置默认参数
			context = context || window
			args = args ? args : []
			//给context新增一个独一无二的属性以免覆盖原有属性
			const key = Symbol()
			context[key] = this
			//通过隐式绑定的方式调用函数
			const result = context[key](...args)
			//删除添加的属性
			delete context[key]
			//返回函数调用的返回值
			return result
		}

		// 自写bind函数
		Function.prototype.myBind = function (context, ...args) {
			const fn = this
			args = args ? args : []
			return function newFn(...newFnArgs) {
				if (this instanceof newFn) {
						return new fn(...args, ...newFnArgs)
				}
				return fn.apply(context, [...args,...newFnArgs])
			}
		}

		// 深拷贝
		// 第一种
		function deepClone(obj){
			let {port1,port2}=new MessageChannel
			port1.postMessage(obj)
			return new Promise(resolve=>{
					port2.onmessage=function(e){
							resolve(e.data)
					}
			})
		}

		// 防抖：两次触发事件的间隔没达到设定的时间，就不触发事件
		const debounce = (fn, wait = 2000, immediate = true) => {
      let timer = 0
      return function(...args) {
        if (timer) clearTimeout(timer)
        if (immediate) {
          if (!timer) fn.apply(this, args)
          timer = setTimeout(() => {
            timer = null
          }, wait)
        } else {
          timer = setTimeout(() => {
            fn.apply(this, args)
          }, wait)
        }
      }
    }
    document.querySelector('#btn').onclick = debounce(() => {
      console.log('间隔2秒才触发事件')
    })

		// 节流：在设定的时间内只触发一次事件
		const throttle = (fn, wait = 2000) => {
      let timer = 0
      return function(...args) {
        if (+new Date() - timer > wait) {
          fn.apply(this, args)
          timer = +new Date()
        }
      }
    }
    document.querySelector('#btn').onclick = throttle(() => {
      console.log('2秒内只触发一次事件')
    })

		// 用递归算法实现，数组长度为5且元素的随机数在2-32间不重复的值
		function add(arr){
				let temp=Math.floor(Math.random()*22+2);
				if(arr.length<5){
						if(arr.indexOf(temp)==-1)arr.push(temp)
						add(arr)
				}
				return arr
		}
		add([])

		// 闭包：内部函数能访问外部函数的变量
		// 闭包的活动对象没有被销毁，一直存在内存
		var name = "The Window";
　	var object = {
　　	name : "My Object",
　　　getNameFunc : function(){
　　　　return function(){
　　　　　return this.name
　　　　}
　　　},
			getNameFunc2 : function(){
　　　	var that = this
　　　　return function(){
　　　　	return that.name
　　　　}
　　	}
　　}
		object.getNameFunc()() // "The Window"
		object.getNameFunc2()() // "My Object"

		// 正序排序
		var arr=[1,3,5,4,2,0,1],arr_=[];
		function sortarr(arr){
				if(arr.length<1)return
				arr_.push(Math.min(...arr))
				arr.splice(arr.indexOf(Math.min(...arr)),1)
				sortarr(arr)
				return arr_
		}
		sortarr(arr)

		// 合并两个数组，有序排序
		function merge(left,right){
				var arr=arr1.concat(arr2),_arr=[];
				while(arr.length>0){
						_arr.push(Math.min.apply(null,arr));
						arr.splice(arr.indexOf(Math.min.apply(null,arr)),1)
				}
				return _arr
		}

		// 类数组转化成数组的方式
		Array.from(arrayLike)
		Array.apply(null, arrayLike)
		Array.prototype.concat.apply([], arrayLike)

		// 判断数据类型的方法
		// typeof，优点：能够快速区分基本数据类型 缺点：不能将Object、Array和Null区分，都返回object
		console.log(typeof 2);               // number
		console.log(typeof true);            // boolean
		console.log(typeof 'str');           // string
		console.log(typeof undefined);       // undefined
		console.log(typeof []);              // object 
		console.log(typeof {});              // object
		console.log(typeof function(){});    // function
		console.log(typeof null);            // object

		// instanceof，优点：能够区分Array、Object和Function，适合用于判断自定义的类实例对象 缺点：Number，Boolean，String基本数据类型不能判断
		console.log(2 instanceof Number);                    // false
		console.log(true instanceof Boolean);                // false 
		console.log('str' instanceof String);                // false  
		console.log([] instanceof Array);                    // true
		console.log(function(){} instanceof Function);       // true
		console.log({} instanceof Object);                   // true


		// Object.prototype.toString.call()，优点：精准判断数据类型 缺点：写法繁琐不容易记，推荐进行封装后使用
		var toString = (para)=>{return Object.prototype.toString.call(para)};
		console.log(toString(2));                      //[object Number]
		console.log(toString(true));                   //[object Boolean]
		console.log(toString('str'));                  //[object String]
		console.log(toString([]));                     //[object Array]
		console.log(toString(function(){}));           //[object Function]
		console.log(toString({}));                     //[object Object]
		console.log(toString(undefined));              //[object Undefined]
		console.log(toString(null));                   //[object Null]

		// Vue生命周期
		/*
		beforeCreate(创建前) vue实例的挂载元素$el和数据对象 data都是undefined, 还未初始化
		created(创建后) 完成了 data数据初始化, el还未初始化
		beforeMount(载入前) vue实例的$el和data都初始化了, 相关的render函数首次被调用
		mounted(载入后) 此过程中进行ajax交互
		beforeUpdate(更新前)
		updated(更新后)
		beforeDestroy(销毁前)
		destroyed(销毁后)
		*/

		// 输入url按回车键后流程
		/*
		域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求，浏览器得到html代码 --> 浏览器解析html代码，并请求html代码中的资源（如js、css、图片等） --> 浏览器对页面进行渲染呈现给用户
		*/

		// 原型链
		/*
		每个构造函数都有一个原型对象，原型对象都包含一个指向构造函数的指针，而实例都包含一个指向原型对象的内部指针。原型对象可以是另一个实例，原型与实例相互连接就组成了原型链。
		*/

		/*回流：当我们对 DOM 的修改引发了 DOM几何尺寸的变化（比如修改元素的宽、高或隐藏元素等）时，浏览器需要重新计算元素的几何属性（其他元素的几何属性和位置也会因此受到影响），然后再将计算的结果绘制出来。这个过程就是回流（也叫重排）。
		重绘：当我们对 DOM的修改导致了样式的变化、却并未影响其几何属性（比如修改了颜色或背景色）时，浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式（跳过了上图所示的回流环节）。这个过程叫做重绘。
		*/

		// 模拟new的过程
		function create(fun,...args){
			// 通过改变对象的 [[Prototype]] 属性会对性能造成严重影响
			// new Object().__proto__ = fun.prototype
			var obj=Object.create(fun.prototype)
			var res=fun.apply(obj,args)
			return res instanceof Object ? res : obj
		}

		// React 和 Vue的区别
		/*
		1.设计思想
				vue的官网中说它是一款渐进式框架，采用自底向上增量开发的设计。
				
				react主张函数式编程，所以推崇纯组件，数据不可变，单向数据流，当然需要双向的地方也可以手动实现，
				比如借助 onChange 和 setState 来实现一个双向的数据流。
		2.编写语法
				Vue推荐的做法是webpack+vue-loader的单文件组件格式，vue保留了html、css、js分离的写法
				
				React的开发者可能知道，react是没有模板的，直接就是一个渲染函数，它中间返回的就是一个虚拟DOM树，
				React推荐的做法是  JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in  js'。
		3.构建工具
				vue提供了CLI 脚手架，可以帮助你非常容易地构建项目。
				
				React 在这方面也提供了 create-react-app，但是现在还存在一些局限性，不能配置等等
		4.数据绑定
				vue是实现了双向数据绑定的mvvm框架，当视图改变更新模型层，当模型层改变更新视图层。
				在vue中，使用了双向绑定技术，就是View的变化能实时让Model发生变化，而Model的变化也能实时更新到View。
				(这里我们可以继续深入讲解一下双向数据绑定的原理，我之前的文章手写Vue源码可参考)
				
				react是单向数据流，react中属性是不允许更改的，状态是允许更改的。
				react中组件不允许通过this.state这种方式直接更改组件的状态。自身设置的状态，可以通过setState来进行更改。
				(注意：React中setState是异步的，导致获取dom可能拿的还是之前的内容，
				所以我们需要在setState第二个参数（回调函数）中获取更新后的新的内容。)
				
				【这里如果你了解深入的话可以尝试描述一下React中setState的异步操作是怎么实现的，Vue中的更新是通过微任务等】
		5.diff算法
				vue中diff算法实现流程：
						1.在内存中构建虚拟dom树
						2.将内存中虚拟dom树渲染成真实dom结构
						3.数据改变的时候，将之前的虚拟dom树结合新的数据生成新的虚拟dom树
						4.将此次生成好的虚拟dom树和上一次的虚拟dom树进行一次比对(diff算法进行比对)，来更新只需要被替换的DOM，
						而不是全部重绘。在Diff算法中，只平层的比较前后两棵DOM树的节点，没有进行深度的遍历。
						5.会将对比出来的差异进行重新渲染
						
				react中diff算法实现流程:
						DOM结构发生改变-----直接卸载并重新create
						DOM结构一样-----不会卸载,但是会update变化的内容
						所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点
						(其实这个key的存在与否只会影响diff算法的复杂度,换言之,你不加key的情况下,
						diff算法就会以暴力的方式去根据一二的策略更新,但是你加了key,diff算法会引入一些另外的操作)
				*/

	</script>
</body>
</html>